package com.ruoyi.flow.service.impl;

import com.ruoyi.flow.domain.Instance;
import com.ruoyi.flow.domain.Trans;
import com.ruoyi.flow.entity.Constant;
import com.ruoyi.flow.entity.FireContext;
import com.ruoyi.flow.entity.machine.*;
import com.ruoyi.flow.entity.vo.FireVo;
import com.ruoyi.flow.exception.FlowException;
import com.ruoyi.flow.listener.FlowListener;
import com.ruoyi.flow.listener.FlowListenerContainer;
import com.ruoyi.flow.service.FlowFireService;
import com.ruoyi.flow.service.FlowInstanceService;
import com.ruoyi.flow.service.FlowMachineService;
import com.ruoyi.flow.service.FlowUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class FlowFireServiceImpl implements FlowFireService {

    @Autowired
    private FlowMachineService flowMachineService;

    @Autowired
    private FlowInstanceService flowInstanceService;

    @Autowired
    private FlowUserService flowUserService;

    @Override
    public void fire(FireVo fireVo) {
        trans(fireVo);
    }


    //状态流转的入口
    private void trans(FireVo fireVo) {
        Flow flow = null;
        Instance instance = null;
        FireContext fireContext = new FireContext();
        fireContext.setModId(fireVo.getModId());
        fireContext.setData(fireVo.getData());
        fireContext.setOpinion(fireVo.getOpinion());

        //新增需要特殊处理，新增时需要先创建流程实例
        if (!StringUtils.isEmpty(fireVo.getInstanceId())) {
            instance = flowInstanceService.getInstance(fireVo.getInstanceId());
            flow = flowMachineService.getFlow(instance.getMachineId());
        } else {
            flow = flowMachineService.getFlow(fireVo.getMachineId());
            instance = flowInstanceService.addInstance(flow);
        }

        Event event = flow.getEventById(fireVo.getEventId());

        if ("add".equalsIgnoreCase(fireVo.getEventId()) || Constant.Event.START.equalsIgnoreCase(fireVo.getEventId())) {
            fireVo.setEventId(Constant.Event.START);
            event.setType(Constant.EventType.OPERATE);
        }

        //判断是否是非流程事件，非流程事件直接结束
        if (event != null && Constant.EventType.OTHER.equals(event.getType())) {
            return;
        }

        Node source = flow.getNodeById(instance.getTargetNodeId());
        fireContext.setFlow(flow);
        fireContext.setInstance(instance);
        fireContext.setSource(source);
        fireContext.setOriginalEvent(event);
        if (event != null && Constant.EventType.APPROVE.equals(event.getType())) {
            fireContext.setResult("1".equals(fireVo.getResult()));
        } else {
            fireContext.setResult(false);
        }

        FlowListener listener = FlowListenerContainer.getListener(fireContext.getFlow().getId());
        //执行监听事件---before
        listener.prepare(fireContext);


        //判断事件类型
        if (event != null && Constant.EventType.APPROVE.equals(event.getType())) { // 审批事件

            //审核通过
            if (fireContext.isResult()) {
                transApprove(fireContext);
            }
            //审核不通过
            else {
                //验证权限
                checkRejectUser(flow, source, event);

                String rejectType = event.getRejectType();

                if (Constant.RejectType.START.equals(rejectType)) {
                    transRejectStart(fireContext);
                } else if (Constant.RejectType.RESTART.equals(rejectType)) {
                    transRejectRestart(fireContext);
                } else if (Constant.RejectType.APPOINT.equals(rejectType)) {
                    transRejectAppoint(fireContext);
                } else {
                    transRejectLast(fireContext);
                }

            }

        } else { // 操作事件
            //开始事件
            if (Constant.Event.START.equals(fireVo.getEventId())) {
                //新增暂时不验证权限

                transStart(fireContext);
            }
            //拿回
            else if (Constant.Event.BACK.equals(fireVo.getEventId())) {

                //找到上一次流转的执行者，需要同一个执行者才允许拿回
                checkBackUser(flow, instance);

                transBack(fireContext);
            }
            //其他事件
            else {
                transApprove(fireContext);
            }

        }
    }


    //新增
    private void transStart(FireContext fireContext) {

        Node source = fireContext.getFlow().getNodes().stream().filter(x -> Constant.NodeType.START.equals(x.getType())).findAny().orElse(null);
        if (source == null) {
            source = fireContext.getFlow().getNodes().get(0);
        }

        fireContext.setSource(source);
        fireContext.setTarget(source);
        Event event = buildStartEvent();
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);

    }

    //审核通过
    private void transApprove(FireContext fireContext) {

        fireContext.setEvent(fireContext.getOriginalEvent());

        List<Line> lines = fireContext.getFlow().getLines().stream().filter(x -> x.getSource().equals(fireContext.getSource().getId()) && x.getOn().equals(fireContext.getEvent().getId())).sorted(Comparator.comparingInt(Line::getSeq)).collect(Collectors.toList());

        boolean ok = false;

        //可能存在分支，需要按需验证分支条件是否成立
        for (Line line : lines) {
            //判断条件是否成立
            String when = line.getWhen();
            if (StringUtils.isEmpty(when)) { // 条件为空，直接成立

                //验证权限
                checkApproveUser(fireContext.getFlow(), fireContext.getSource(), line);

                Node target = fireContext.getFlow().getNodeById(line.getTarget());
                fireContext.setTarget(target);
                fireContext.setLine(line);
                transNode(fireContext);
                ok = true;
                break;
            } else {
                if (condition(fireContext, fireContext.getFlow().getConditionById(when))) { // 判断条件是否成立

                    //验证权限
                    checkApproveUser(fireContext.getFlow(), fireContext.getSource(), line);

                    Node target = fireContext.getFlow().getNodeById(line.getTarget());
                    fireContext.setTarget(target);
                    fireContext.setLine(line);
                    transNode(fireContext);
                    ok = true;
                    break;
                }
            }
        }

        if (!ok) {
            throw new FlowException("未找到有效流转");
        }
    }

    //驳回到上个节点
    private void transRejectLast(FireContext fireContext) {
        Node target = fireContext.getFlow().getNodeById(fireContext.getInstance().getSourceNodeId());
        Event event = buildRejectEvent();
        fireContext.setTarget(target);
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);
    }

    //驳回到开始节点
    private void transRejectStart(FireContext fireContext) {
        Node target = fireContext.getFlow().getNodes().stream().filter(x -> Constant.NodeType.START.equals(x.getType())).findAny().orElse(null);
        if (target == null) {
            target = fireContext.getFlow().getNodes().get(0);
        }
        Event event = buildRejectStartEvent();
        fireContext.setTarget(target);
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);
    }

    //驳回到重新开始节点
    private void transRejectRestart(FireContext fireContext) {
        Node target = fireContext.getFlow().getNodes().stream().filter(x -> Constant.NodeType.RESTART.equals(x.getType())).findAny().orElse(null);
        if (target == null) {
            target = fireContext.getFlow().getNodes().get(0);
        }
        Event event = buildRejectRestartEvent();
        fireContext.setTarget(target);
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);
    }

    //驳回到指定节点
    private void transRejectAppoint(FireContext fireContext) {
        Node target = fireContext.getFlow().getNodeById(fireContext.getEvent().getRejectNode());
        Event event = buildRejectEvent();
        fireContext.setTarget(target);
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);
    }

    //拿回
    private void transBack(FireContext fireContext) {
        Node target = fireContext.getFlow().getNodeById(fireContext.getInstance().getSourceNodeId());
        Event event = buildBackEvent();
        fireContext.setTarget(target);
        fireContext.setEvent(event);

        //流转
        transNode(fireContext);
    }

    //指定节点流转
    private void transNode(FireContext fireContext) {

        FlowListener listener = FlowListenerContainer.getListener(fireContext.getFlow().getId());
        //执行监听事件---before
        listener.before(fireContext);

        //主要就是处理这三张表数据：流转记录表、流转表、实例表

        String eventId = fireContext.getEvent().getId();

        if (Constant.Event.START.equals(eventId)) { // 开始事件

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //写入流转表
            flowInstanceService.addTrans(fireContext);

        } else if (Constant.Event.BACK.equals(eventId)) { // 拿回事件

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //删除流转表最后一条记录（但是流转表至少保留一条记录）
            List<Trans> transList = flowInstanceService.getTransList(fireContext.getInstance().getInstanceId());
            if (transList.size() > 1) {
                Trans trans = transList.get(transList.size() - 1);
                flowInstanceService.deleteTrans(trans.getTransId());
            }

        } else if (Constant.Event.REJECT_START.equals(eventId)) { // 驳回到开始节点

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //先清空流转表，再写入流转表（这里写入与开始事件类型）
            flowInstanceService.clearTrans(fireContext.getInstance().getInstanceId());
            flowInstanceService.addTrans(fireContext);

        } else if (Constant.Event.REJECT_RESTART.equals(eventId)) { // 驳回到重新开始节点

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //先清空流转表，再写入流转表（这里写入与开始事件类型）
            flowInstanceService.clearTrans(fireContext.getInstance().getInstanceId());
            flowInstanceService.addTrans(fireContext);

        } else if (Constant.Event.REJECT.equals(eventId)) { // 驳回到指定节点或上个节点

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //删除流转表记录（依次删除流转表记录，知道删除targetNodeId和驳回的指定节点一致时停止，但是流转表至少保留一条记录）
            List<Trans> transList = flowInstanceService.getTransList(fireContext.getInstance().getInstanceId());
            for (int i = transList.size() - 1; i > 0; i--) {
                if (transList.get(i).getTargetNodeId().equals(fireContext.getTarget().getId())) {
                    break;
                }
                flowInstanceService.deleteTrans(transList.get(i).getTransId());
            }

        } else { // 其他事件

            //写入流转记录表
            flowInstanceService.addTransLog(fireContext);
            //写入流转表
            if (!fireContext.getSource().getId().equals(fireContext.getTarget().getId())) {
                flowInstanceService.addTrans(fireContext);
            }

        }

        //根据流转表最后一条记录更新流程实例表，主要更新：sourceNodeId、targetNodeId、eventId、lindeId等
        flowInstanceService.updateInstance(fireContext.getInstance().getInstanceId());

        //执行监听事件---after
        listener.after(fireContext);
    }


    //===============辅助方法==============

    private boolean condition(FireContext fireContext, Condition condition) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        ctx.setVariable("context", fireContext);
        return (Boolean) parser.parseExpression(condition.getCondition()).getValue(ctx);
    }

    private void checkRejectUser(Flow flow, Node source, Event event) {
        if (flowUserService.isAdmin()) {
            return;
        }

        List<Handler> handlers = new ArrayList<>();
        List<Line> lines = flow.getLines().stream().filter(x -> x.getSource().equals(source.getId()) && x.getOn().equals(event.getId())).collect(Collectors.toList());
        for (Line line : lines) {
            if (line.getHandlers() != null && line.getHandlers().size() > 0) {
                handlers.addAll(line.getHandlers());
            } else {
                handlers.addAll(source.getHandlers());
            }
        }

        if (checkUser(handlers)) {
            return;
        }

        throw new FlowException("当前用户无权限操作");
    }

    private void checkApproveUser(Flow flow, Node source, Line line) {
        if (flowUserService.isAdmin()) {
            return;
        }

        List<Handler> handlers = null;
        if (line.getHandlers() != null && line.getHandlers().size() > 0) {
            handlers = line.getHandlers();
        } else {
            handlers = source.getHandlers();
        }

        if (checkUser(handlers)) {
            return;
        }

        throw new FlowException("当前用户无权限操作");

    }

    private void checkBackUser(Flow flow, Instance instance) {
        if (flowUserService.isAdmin()) {
            return;
        }

        List<Trans> transList = flowInstanceService.getTransList(instance.getInstanceId());
        Trans trans = transList.get(transList.size() - 1);
        Node source = flow.getNodeById(trans.getSourceNodeId());
        Line line = flow.getLineById(trans.getLineId());

        checkApproveUser(flow, source, line);
    }

    private boolean checkUser(List<Handler> handlers) {
        return flowUserService.checkUser(handlers);
    }

    //验证权限
    private Event buildStartEvent() {
        Event event = new Event();

        event.setId(Constant.Event.START);
        event.setName("开始");
        event.setType(Constant.EventType.OPERATE);

        return event;
    }

    private Event buildRejectEvent() {
        Event event = new Event();

        event.setId(Constant.Event.REJECT);
        event.setName("驳回");
        event.setType(Constant.EventType.OPERATE);

        return event;
    }

    private Event buildRejectStartEvent() {
        Event event = new Event();

        event.setId(Constant.Event.REJECT_START);
        event.setName("驳回开始");
        event.setType(Constant.EventType.OPERATE);

        return event;
    }

    private Event buildRejectRestartEvent() {
        Event event = new Event();

        event.setId(Constant.Event.REJECT_RESTART);
        event.setName("驳回重新开始");
        event.setType(Constant.EventType.OPERATE);

        return event;
    }

    private Event buildBackEvent() {
        Event event = new Event();

        event.setId(Constant.Event.BACK);
        event.setName("拿回");
        event.setType(Constant.EventType.OPERATE);

        return event;
    }

}
